'''
Given one set of parameters, run tests to fill one graph.

'''

import CERTMT_TaskSystem2 as cert
import numpy as np
from random import *


#constants corresponding to different schedulers
BASELINE=cert.BASELINE
CERT_MT=cert.CERT_MT
RUNTIME = cert.RUNTIME

FAIL=cert.FAIL

NUM_METHODS=2

FIXED = cert.FIXED
EXPO = cert.EXPO

INDEX_THE_BINS = 0
INDEX_SUCCESS_SUMMARY = 1
INDEX_FAIL_SUMMARY = 2
INDEX_ALL_FAIL_COUNTERS = 3


'''
this is a guess.  Don't know what's really the best thing here.
Probably more efficient to maximize graph parallelism, but I do have a strict 
time limit.
0 is the default, i.e. let Gurobi figure it out
'''
THREADS_PER_TEST=0


#pretty_print = {BASELINE: "baseline", SMT_ONE_FRAME: "SMT with One Frame Size", CERT_MT: "SMT with Multiple Frame Sizes", FULL_P_CERT_MT: "SMT with Full Premption"}

def runParamTest(maxTotalUtil, minTotalUtil, bin_size, min_samples,
                 utilMin, utilMax, expected_score, slope, prob_harm, degree_harm, score_distro, core_count, periodMin, periodCount, timeout):
    runtimes_success = []
    runtimes_failure = []
    count_fail_total = 0
    count_fail_fast = 0
    count_fail_timeout = 0
    count_fail_normal = 0
    count_success = 0
    fail_no_timeout_counter = 0
    threadsPerTest=THREADS_PER_TEST

    test_range = maxTotalUtil - minTotalUtil
    numBins = int(np.ceil(test_range/bin_size))
    # "theBins": index 0 is number of task sets in bin, following indicies are how many of those are schedulable for various algorithms
    theBins = []
    for i in range(0, numBins):
        theBins.append(0)
        theBins[i]=[0]*(NUM_METHODS+1)
        #theBins.append([0] * NUM_METHODS +1)

    # Iterate until we've tried at least 'min_samples' task sets at our base utilization
    coreCountBin=int(np.floor((core_count-minTotalUtil)/bin_size))
    while theBins[coreCountBin][0] < min_samples:
        runBaseline=False
        runCertMT=True
        # create task set
        # only parameter I needed to add was slope
        # def __init__(self, setUtil, utilMin, utilMax, expected_score, slope, prob_harm, degree_harm, score_distro, core_count,
        #                 periodMin, periodCount, timeout, solutionLimit, lowerBound, upperBound, threadsPerTest):
        myTaskSet = cert.TaskSet(minTotalUtil, utilMin, utilMax, expected_score, slope, prob_harm, degree_harm, score_distro, core_count,
                                           periodMin, periodCount, timeout, 1000, 1, 1, threadsPerTest)

        # Loop until we reach a task set that nothing can schedule
        while True:
            # Depending on where our utilization fell as we added tasks, we choose a bin
            #this is the starting bin
            myBin = int(np.floor((myTaskSet.totalUtil - minTotalUtil) / bin_size))
            #break if we overshoot desired range
            if len(theBins)-1 < myBin:
                break #out of while TRUE
            result=myTaskSet.testSystem(runBaseline, runCertMT)
            # record that we just attempted another test
            theBins[myBin][0]+=1
            #record baseline success
            if result[BASELINE]:
                theBins[myBin][BASELINE] += 1
            else:
                runBaseline=False
            # record CERT_MT success
            if result[CERT_MT]:
                theBins[myBin][CERT_MT] += 1
                runtimes_success.append(result[RUNTIME])
                #success_counter += 1
                count_success += 1
            else:
                runCertMT=False
                runtimes_failure.append(result[RUNTIME])
                if result[RUNTIME] < timeout:
                    count_fail_normal += 1
                else:
                    count_fail_timeout +=1
                count_fail_total += 1
            #for testing baseline only
            #runCertMT=False
            if not(runBaseline or runCertMT):
                break #out of while True; we didn't have any success
            else:
                myTaskSet.addTask()

        # If we reach a task set that's unschedulable, assume it's unschedulable for higher utilizations.
        # (We could just simulate all the higher-utilization bins, but that's slow.)
        totalUtil = myTaskSet.totalUtil + uniform(utilMin, utilMax)
        while totalUtil < maxTotalUtil:
            myBin=int(np.floor((totalUtil-minTotalUtil) /bin_size))
            #myBin = int(np.floor((myTaskSet.totalUtil - minTotalUtil) / bin_size))
            theBins[myBin][0]+=1
            totalUtil = totalUtil +uniform(utilMin, utilMax)
            count_fail_fast += 1
            count_fail_total += 1
            runtimes_failure.append(0)
        #print("utilMin, utilMax, symMean, symDev", minTaskUtil, maxTaskUtil, symMean, symDev)
        #print("theBins[0][0]=",theBins[0][0])
        #print("theBins=", theBins)
        #go back and start a new task set
        #print(theBins)
    #return(theBins, runtimes_success, runtimes_failure, timeout_counter, success_counter, fail_no_timeout_counter)
    return(theBins,
           (count_success, max(runtimes_success), np.mean(runtimes_success), np.median(runtimes_success)),
           (count_fail_total, max(runtimes_failure), np.mean(runtimes_failure), np.median(runtimes_failure)),
           (count_fail_fast, count_fail_timeout, count_fail_normal)
           )

def main():
    #parameterrs for testing
    minTotalUtil=1
    maxTotalUtil=4
    bin_size=0.25
    min_samples=5
    minTaskUtil=.3
    maxTaskUtil=.7
    #need to prevents costs<1
    periodMin=10
    periodCount=4
    #symDistrib=SPLIT_UNIFORM
    #symParam1=.1
    #symParam2=.8
    #symParam3=.1
    core_count=2
    timeout=10
    #new parameters
    expected_score = 0.4
    slope = 0
    prob_harm = 0
    degree_harm =2
    score_distro = FIXED
    
    
    #maxTotalUtil, minTotalUtil, bin_size, min_samples,
    #utilMin, utilMax, expected_score, prob_harm, degree_harm, 
    #score_distro, core_count, periodMin, periodCount, symParam1, symParam2, symParam3, symDistrib, coreCount, timeout
    
    #runParamTest(minTotalUtil, maxTotalUtil, bin_size, min_samples, minTaskUtil, maxTaskUtil, periodMin, periodCount, symParam1, symParam2, symParam3, symDistrib, coreCount, timeout)

    results = runParamTest(maxTotalUtil, minTotalUtil, bin_size, min_samples,
                 minTaskUtil, maxTaskUtil, expected_score, slope, prob_harm, degree_harm,
                 score_distro, core_count, periodMin, periodCount, timeout)

    #(theBins,
    #(count_success, max(runtimes_success), np.mean(runtimes_success), np.median(runtimes_success)),
    #(count_fail_total, max(runtimes_failure), np.mean(runtimes_failure), np.median(runtimes_failure)),
    #(count_fail_total, count_fail_fast, count_fail_timeout, count_fail_normal)
    #)

    theBins = results[0]
    print(theBins)

    success_data = results[1]
    print("Success data: count, max, mean, median:")
    for s in success_data:
        print(str(s), end=" ")
    print()
    fail_data = results[2]
    print("Fail data: total count, max, mean, median:")
    for f in fail_data:
        print(str(f), end=" ")
    print()
    fail_counters = results [3]
    print("Fail counters: total, fast, timeout, normal")
    for c in fail_counters:
        print(str(c), end=" ")

if __name__== "__main__":
     main() 



#test=runParamTest(minTotalUtil, maxTotalUtil, binSize, minSamples, minTaskUtil, maxTaskUtil, periodMin, periodCount, symMean, symDev, symDistrib, coreCount, timeout)
